-----------------------------------------------------------------
-- whnet：和网络相关的函数
-- 目前功能：
-- tcp连接的创建
-- udp连接的创建
-----------------------------------------------------------------

module(..., package.seeall)

local socket = require("socket")
local base64 = require("base64")

-----------------------------------------------------------------
-- 从url中获取网站地址
-- 返回: addr,port
-----------------------------------------------------------------
function getAddrPortFromURL(url)
	-- 默认端口为80
	local	port	= 80
	-- 先看看有没有'://'，有的话前面的看看是什么协议
	local	p1,p2	= string.find(url, '://')
	if( p1 ) then
		local	s	= string.sub(url, 1, p1-1)
		if( s=='https' ) then
			port	= 443
		end
		url			= string.sub(url, p2+1)
	end
	-- 把第一部分('/'之前的部分。如果没有就说明本身就是一个地址)取出
	p1,p2			= string.find(url, '/')
	if( p1 ) then
		url			= string.sub(url, 1, p1-1)
	end
	-- 看看这个地址有没有port的部分
	p1,p2			= string.find(url, ':')
	if( p1 ) then
		port		= tonumber(string.sub(url,p1+1))
		url			= string.sub(url, 1, p1-1)
	end
	return	url,port
end

-----------------------------------------------------------------
-- 测试TCP端口是否可用
-----------------------------------------------------------------
function tryConnectTCPServer( addr, port, timeout )
	if( not timeout ) then
		-- timeout 单位是秒
		timeout	= 2
	end
	-- Make Connection
	local	sock , err = socket.tcp()
	if( not sock ) then
		return	false, err
	end
	sock:settimeout(timeout)
	local	rst,err	= sock:connect( addr, port )
	if( not rst ) then
		sock:close()
		return	false, err
	end
	sock:close()
	return	true
end

-----------------------------------------------------------------
-- tcp服务器和指令解析模版
-----------------------------------------------------------------
-- 一次只处理一个连接
-- 所有指令必须以\n结尾，否则服务器会阻塞住
-- 使用者需要实现onMsg(msg)函数以处理消息
-- 使用者需要实现onAccept和onClose函数以处理连接建立和关闭的情形
function createTCPServer()
	local svr	= {}
	-- 开启服务
	function svr:startServer( port, reuseaddr )
		-- 为了保险，先关闭一下
		self:endServer()

		-- Create Socket
		local sock , err = socket.tcp()
		local backlog = 0
	
		-- Check Socket
		if sock == nil then 
			return false , err
		end
		-- Allow Address Reuse
		if( reuseaddr ) then
			sock:setoption( "reuseaddr" , true )
		end
		-- Bind Socket
		local res, err = sock:bind( "*" , port )
		if res == nil then
			sock:close()
			return false , err
		end
	
		-- Wait for Connection
		local rst , err = sock:listen( backlog )
		if rst == nil then 
			sock:close()
			return false , err
		end

		-- save the socket
		self.sock	= sock
		-- 设置为每帧检查
		Runtime:addEventListener("enterFrame", self)
		return true
	end
	-- 结束服务
	function svr:endServer()
		Runtime:removeEventListener("enterFrame", self)
		if( self.client ) then
			self.client:close()
			self.client	= nil
		end
		if( self.sock ) then
			self.sock:close()
			self.sock	= nil
		end
	end
	-- enterframe时需要调用这个
	function svr:enterFrame(event)
		if( self.client ) then
			-- 读取到没有为止（因为可能一个frame发来多条指令的）
			local	s	= socket.select({self.client}, nil, 0)
			while #s>0  do
				-- 一次一行
				local msg , _ = self.client:receive('*l')
				if( msg ) then
					-- 调用相关的处理函数（onMsg是必须实现的）
					if( not _dealMsgRecvFile(self, msg) ) then
						self:onMsg(msg)
					end
					-- 看看还有木有
					s	= socket.select({self.client}, nil, 0)
				else
					if( type(self.onClose)=="function" ) then
						self:onClose()
					end
					-- 关闭了
					self.client:close()
					self.client	= nil
					s	= {}
				end
			end
		else
			self.sock:settimeout(0)
			self.client	= self.sock:accept()
			if( self.client ) then
				-- 期望能立即发出
				self.client:setoption("tcp-nodelay",true)
				-- 如果有onAccept就调用一下
				if( type(self.onAccept)=="function" ) then
					self:onAccept()
				end
			end
		end
	end
	-- 发送信息
	function svr:sendMsg(msg)
		if( self.client ) then
			return	self.client:send(msg.."\n")
		end
	end
	function svr:getpeername()
		if( self.client ) then
			return	self.client:getpeername()
		end
	end
	--
	svr.sendFile	= _sendFile
	-- Return Server
	return svr
end

-----------------------------------------------------------------
-- tcp客户端和指令解析模版
-----------------------------------------------------------------
-- 所有指令都会自动添加\n结尾
-- 使用者需要实现onMsg(msg)函数以处理服务器返回的消息
-- 使用者需要实现onAccept和onClose函数以处理连接建立和关闭的情形
function createTCPClient()
	local	client	= {}

	function client:enterFrame(event)
		-- 能到这里说明client一定有值了
		-- 读取到没有为止（因为可能一个frame发来多条指令的）
		local	s	= socket.select({self.sock}, nil, 0)
		while #s>0  do
			-- 一次一行
			local msg , _ = self.sock:receive('*l')
			if( msg ) then
				-- 调用相关的处理函数（onMsg是必须实现的）
				if( not _dealMsgRecvFile(self, msg) ) then
					self:onMsg(msg)
				end
				-- 看看还有木有
				s	= socket.select({self.sock}, nil, 0)
			else
				-- 关闭了
				self:close()
				s	= {}
			end
		end
	end
	function client:connect( addr, port, timeout )
		-- 为了保险，关闭一下上一个连接
		self:close()
		if( not timeout ) then
			-- timeout 单位是秒
			timeout	= 2
		end
		-- Make Connection
		self.sock , err = socket.tcp()
		if( not self.sock ) then
			return	false, err
		end
		self.sock:settimeout(timeout)
		local	rst,err	= self.sock:connect( addr, port )
		if( not rst ) then
			self.sock:close()
			self.sock		= nil
			return	false, err
		end
		-- 期望能立即发出
		self.sock:setoption("tcp-nodelay",true)
		if( type(self.onAccept)=="function" ) then
			self:onAccept()
		end
		Runtime:addEventListener("enterFrame", self)
		self.closing	= false
		return	true
	end
	function client:close()
		if( not self.closing and self.sock ) then
			self.closing	= true
			self.sock:close()
			Runtime:removeEventListener("enterFrame", self)
			-- 这个放到指针清空后面，这样就算里面循环调用close，也不会造成死循环了
			if( type(self.onClose)=="function" ) then
				self:onClose()
			end
			-- 放到这里，以免onClose中需要用这个来做指标
			self.sock	= nil
		end
	end
	-- 发送信息
	function client:sendMsg(msg)
		return	self.sock:send(msg.."\n")
	end
	function client:getpeername()
		if( self.sock ) then
			return	self.sock:getpeername()
		end
	end

	client.sendFile	= _sendFile

	return	client
end

-----------------------------------------------------------------
-- udp服务器和指令解析模版
-----------------------------------------------------------------
function createUDPServer()
	local	udp	= {}
	function udp:enterFrame(event)
--print("**** enterFrame "..event.time)
		-- sock一定是有效的
		local	msg,lastip,lastport	= 1,nil,0
		while( msg ) do
			msg, lastip, lastport	= udp.sock:receivefrom()
			if( msg ) then
				self.lastip, self.lastport	= lastip,lastport
				-- onMsg是一定要有的
				if( msg=="cmd=hello" ) then
					-- 猜想，如果设置timeout==0，连续发送太快可能后面的会覆盖前面的？!!!!
					self.sock:settimeout(1)
					self:sendMsg("cmd=hello")
					self.sock:settimeout(0)
					if( type(self.onAccept)=="function" ) then
						self:onAccept()
					end
				elseif( msg=="cmd=bye" ) then
					if( type(self.onClose)=="function" ) then
						self:onClose()
					end
				else
					if( not _dealMsgRecvFile(self, msg) ) then
						self:onMsg(msg)
					end
				end
			end
		end
	end
	function udp:startServer(port, reuseaddr)
		-- 保险起见
		self:endServer()
		-- 创建socket
		local	sock,err	= socket.udp()	
		if( not sock ) then
			return false, err
		end
		-- Allow Address Reuse
		if( reuseaddr ) then
			sock:setoption( "reuseaddr" , true )
		end

		-- bind to port
		local	rst,err	= sock:setsockname("*",port)
		if( not rst ) then
			sock:close()
			return	false, err
		end
		sock:settimeout(0)
		self.sock	= sock
		-- 设置为每帧检查
		Runtime:addEventListener("enterFrame", self)
		return	true
	end
	function udp:endServer()
		if( self.sock ) then
			-- 发送结束包
			self:sendMsg("cmd=bye")
			self.sock:close()
			self.sock	= nil
			Runtime:removeEventListener("enterFrame", self)
		end
	end

	-- 发送
	function udp:sendMsg(msg)
		if( self.lastip ) then
			return	self.sock:sendto(msg, self.lastip, self.lastport)
		else
			print("**** udp:sendMsg with no lastip!")
		end
	end
	function udp:getpeername()
		return	self.lastip
	end
	udp.sendFile	= _sendFile

	return	udp
end

function createUDPClient()
	local	udp	= {}
	function udp:enterFrame(event)
		-- sock一定是有效的，而且是connected的
		local	msg	= 1
		while( msg and self.sock ) do
			msg	= self.sock:receive()
			if( msg ) then
				if( msg=="cmd=bye" ) then
					if( type(self.onClose)=="function" ) then
						self:onClose()
					end
				else
					-- onMsg是一定要有的
					if( not _dealMsgRecvFile(self, msg) ) then
						self:onMsg(msg)
					end
				end
			end
		end
	end

	function udp:connect( addr, port, timeout )
		-- 为了保险，关闭一下上一个连接
		self:close()
		if( not timeout ) then
			-- timeout 单位是秒
			timeout	= 2
		end
		-- Make Connection
		local	sock , err = socket.udp()
		if( not sock ) then
			return	false, err
		end

		sock:setpeername( addr, port )
		sock:settimeout(timeout)
		self.sock	= sock
		-- 其实为了保险可以模拟一个连接过程（比如发送HELLO，然后等待HELLO返回）
		-- 还是必须要发送个hello的，因为对方需要知道自己的IP
		self:sendMsg("cmd=hello")
		-- 等待对方返回hello
		local	msg,err	= self.sock:receive()
		if( not msg or msg:sub(1,4)~="cmd=" ) then
			-- 连接失败
			self.sock:close()
			self.sock	= nil
			if( msg~=nil ) then
				err	= "whnet:Wrong server response: "..msg
			end
			return	false, err
		end
		sock:settimeout(0)
		-- 不知道为啥，有时候收不到服务器返回的hello，而变成了onAccept中发出的信息，感觉冲掉了前面的!!!!
		if( msg~="cmd=hello" ) then
			self:onMsg(msg)
		end

		Runtime:addEventListener("enterFrame", self)
		self.closing	= false
		return	true
	end
	function udp:close()
		if( self.sock and not self.closing ) then
			-- 发送结束包
			self:sendMsg("cmd=bye")
			self.closing	= true
			self.sock:close()
			Runtime:removeEventListener("enterFrame", self)
			if( type(self.onClose)=="function" ) then
				self:onClose()
			end
			-- 放到这里，以免onClose中需要用这个来做指标
			self.sock	= nil
		end
	end
	-- 发送信息
	function udp:sendMsg(msg)
		return	self.sock:send(msg)
	end
	function udp:getpeername()
		if( self.sock ) then
			return	self.sock:getpeername()
		end
	end
	udp.sendFile	= _sendFile

	return	udp
end

------------------------------------------------------------------------------------------------------------------------
-- 发送文件（只利用sendMsg接口所以，UDP和TCP，服务器和客户端都可以用）
------------------------------------------------------------------------------------------------------------------------
function _sendFile(self, filename, basedir)
	-- 二进制打开文件
	local loadpath	= system.pathForFile( filename, basedir )
	local hbFile	= io.open(loadpath, "rb");
	if( not hbFile ) then
		-- 打开失败
		return	false
	end

	-- 发送文件头指令
	self:sendMsg("fname="..filename)
	-- 每次读取512字节，发送base64文件数据
	while(true) do
        local block	= hbFile:read(512)
        if(not block)then break;end
		local b64	= base64.enc(block)
		self:sendMsg("fdata="..b64)
	end
	-- 发送文件结束指令（这些都是为了凑够5个字符）
	self:sendMsg("f=EOF")

	-- 关闭文件
	hbFile:close()
end
-- 接受文件的回调内容（在onMsg中先调用这个，返回真表示是文件相关的指令）
function _dealMsgRecvFile(self, msg)
	local	cmd	= msg:sub(1,5)
	if( "fname"==cmd ) then
		self._filerecv	= {cmd="file", name=msg:sub(7), data=""}
	elseif( "fdata"==cmd ) then
		self._filerecv.data	= self._filerecv.data..base64.dec(msg:sub(7))
	elseif( "f=EOF"==cmd ) then
		-- 把这个传给对应的onFile函数
		if( type(self.onFile)=="function" ) then
			self:onFile(self._filerecv)
		end
		-- 清空指令释放空间
		self._filerecv	= nil
	else
		return	false
	end
	return	true
end
